﻿using System.Collections;
using System.ComponentModel;
using System.Drawing;

using Storm.TextEditor.Editor;
using Storm.TextEditor.Editor.Text;
using Storm.TextEditor.Editor.Undo;
using Storm.TextEditor.Parser.Objects.Collections;
using System;

namespace Storm.TextEditor.Parser.Objects
{
	/// <summary>
	/// Represents a row in a document.
	/// </summary>
	public sealed class Row
		: IEnumerable
	{
		#region Fields

		private string         text  = "";
		private WordCollection words = new WordCollection();

		private WordCollection       formattedWords = new WordCollection();
		private SegmentCollection    startSegments  = new SegmentCollection();
		private SegmentCollection    endSegments    = new SegmentCollection();
		private SyntaxDocument       document       = null;
		private ImageIndexCollection images         = new ImageIndexCollection();

		private Segment startSegment = null;
		private Segment endSegment   = null;

		private RowState rowState  = Objects.RowState.NotParsed;
		private Color    backColor = Color.Transparent;

		private object tag            = null;
		private bool   inQueue        = false;
		private bool   inKeywordQueue = false;
		private int    indent         = 0;

		private bool highlight  = true;
		private bool hovered    = false;
		private bool bookmarked = false;
		private bool breakpoint = false;

		private Segment expansionStartSegment = null;
		private Segment expansionEndSegment   = null;

		private int expansionPixelStart = 0;
		private int expansionStartChar  = 0;
		private int expansionPixelEnd   = 0;
		private int expansionEndChar    = 0;

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the expansion ending character of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the expansion ending character of the Row.")]
		public int ExpansionEndChar
		{
			get { return expansionEndChar; }
			set { expansionEndChar = value; }
		}
        
		/// <summary>
		/// Gets or sets the expansion pixel end of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the expansion pixel end of the Row.")]
		public int ExpansionPixelEnd
		{
			get { return expansionPixelEnd; }
			set { expansionPixelEnd = value; }
		}
        
		/// <summary>
		/// Gets or sets the expansion starting character of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the expansion starting character of the Row.")]
		public int ExpansionStartChar
		{
			get { return expansionStartChar; }
			set { expansionStartChar = value; }
		}
        
		/// <summary>
		/// Gets or sets the expansion pixel start of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the expansion pixel start of the Row.")]
		public int ExpansionPixelStart
		{
			get { return expansionPixelStart; }
			set { expansionPixelStart = value; }
		}
        
		/// <summary>
		/// Gets or sets the level of indentation the Row is currently on.
		/// </summary>
		[Browsable(false)]
		[Category("Appearance")]
		[Description("Gets or sets the level of indentation the Row is currently on.")]
		public int Indent
		{
			get { return indent; }
			set { indent = value; }
		}
        
		/// <summary>
		/// Gets or sets whether the Row is currently in its parent document's keyword parsing queue.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets whether the Row is currently in its parent document's keyword parsing queue.")]
		public bool InKeywordQueue
		{
			get { return inKeywordQueue; }
			set { inKeywordQueue = value; }
		}
        
		/// <summary>
		/// Gets or sets whether the Row is currently in its parent document's parsing queue.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets whether the Row is currently in its parent document's parsing queue.")]
		public bool InQueue
		{
			get { return inQueue; }
			set { inQueue = value; }
		}
        
		/// <summary>
		/// Gets or sets the Row's tag.
		/// </summary>
		[Browsable(true)]
		[Category("Data")]
		[Description("Gets or sets the Row's tag.")]
		public object Tag
		{
			get { return tag; }
			set { tag = value; }
		}
        
		/// <summary>
		/// Gets or sets the expansion end segment of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the expansion end segment of the Row.")]
		public Segment ExpansionEndSegment
		{
			get { return expansionEndSegment; }
			set { expansionEndSegment = value; }
		}

		/// <summary>
		/// Gets or sets the expansion start segment of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the expansion start segment of the Row.")]
		public Segment ExpansionStartSegment
		{
			get { return expansionStartSegment; }
			set { expansionStartSegment = value; }
		}
        
		/// <summary>
		/// Gets or sets the end segment of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the end segment of the Row.")]
		public Segment EndSegment
		{
			get { return endSegment; }
			set { endSegment = value; }
		}
        
		/// <summary>
		/// Gets or sets the start segment of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the start segment of the Row.")]
		public Segment StartSegment
		{
			get { return startSegment; }
			set { startSegment = value; }
		}
        
		/// <summary>
		/// Gets or sets the images of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Appearance")]
		[Description("Gets or sets the images of the Row.")]
		public ImageIndexCollection Images
		{
			get { return images; }
			set { images = value; }
		}
        
		/// <summary>
		/// Gets or sets the parent document of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the parent document of the Row.")]
		public SyntaxDocument Document
		{
			get { return document; }
			set { document = value; }
		}
        
		/// <summary>
		/// Gets or sets the collection of segments that contains the ending segments of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the collection of segments that contains the ending segments of the Row.")]
		public SegmentCollection EndSegments
		{
			get { return endSegments; }
			set { endSegments = value; }
		}

		/// <summary>
		/// Gets or sets the collection of segments that contains the starting segments of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the collection of segments that contains the starting segments of the Row.")]
		public SegmentCollection StartSegments
		{
			get { return startSegments; }
			set { startSegments = value; }
		}
        
		/// <summary>
		/// Gets or sets the words of the Row that are formatted.
		/// </summary>
		[Browsable(false)]
		[Category("Appearance")]
		[Description("Gets or sets the words of the Row that are formatted.")]
		public WordCollection FormattedWords
		{
			get { return formattedWords; }
			set { formattedWords = value; }
		}

		/// <summary>
		/// Gets or sets the collection of words in the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Appearance")]
		[Description("Gets or sets the collection of words in the Row.")]
		internal WordCollection Words
		{
			get { return words; }
			set { words = value; }
		}

		/// <summary>
		/// Gets or sets the current RowState of the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets the current RowState of the Row.")]
		public RowState RowState
		{
			get { return rowState; }
			set
			{
				if (value == rowState)
					return;

				if (value == Objects.RowState.SegmentParsed && inKeywordQueue == false)
				{
					document.KeywordQueue.Add(this);
					inKeywordQueue = true;
				}

				if ((value == Objects.RowState.AllParsed || value == Objects.RowState.NotParsed) &&
					inKeywordQueue == false)
				{
					document.KeywordQueue.Remove(this);
					inKeywordQueue = false;
				}

				rowState = value;
			}

		}

		/// <summary>
		/// Gets the 'depth' of the Row - basically the indentation level.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets the 'depth' of the Row - basically the indentation level.")]
		public int Depth
		{
			get
			{
				int indent = 0;
				Segment segment = startSegment;
				while (segment != null)
				{
					if (segment.Scope != null && segment.Scope.CauseIndent)
						indent++;

					segment = segment.Parent;
				}

				if (this.ShouldOutdent == true)
					indent--;

				return indent;
			}
		}

		/// <summary>
		/// Gets or sets whether the Row should be outdented.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets whether the Row should be outdented.")]
		public bool ShouldOutdent
		{
			get
			{
				if (startSegment.EndRow == this)
				{
					if (startSegment.Scope.CauseIndent == true)
						return true;
				}

				return false;
			}
		}

		/// <summary>
		/// Gets or sets the background color of the Row.
		/// </summary>
		[Browsable(true)]
		[Category("Appearance")]
		[Description("Gets or sets the background color of the Row.")]
		public Color BackColor
		{
			get { return backColor; }
			set { backColor = value; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="Row"/> is hovered.
		/// </summary>
		/// <value><c>true</c> if hovered; otherwise, <c>false</c>.</value>
        [Browsable(false)]
        [Category("Data")]
        [Description("Gets or sets a value indicating whether this Row is hovered.")]
		public bool Hovered
		{
			get { return hovered; }
			set { hovered = value; }
		}

		/// <summary>
		/// Gets or sets whether the Row is bookmarked.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets whether the Row is bookmarked.")]
		public bool Bookmarked
		{
			get { return bookmarked; }
			set
			{
				bookmarked = value;

				if (value == true)
					document.InvokeBookmarkAdded(this);
				else
					document.InvokeBookmarkRemoved(this);

				document.InvokeChange();
			}
		}

		/// <summary>
		/// Gets or sets whether the Row has a breakpoint.
		/// </summary>
		[Browsable(false)]
		[Category("Behaviour")]
		[Description("Gets or sets whether the Row has a breakpoint.")]
		public bool Breakpoint
		{
			get { return breakpoint; }
			set
			{
				breakpoint = value;

				if (value == true)
					document.InvokeBreakpointAdded(this);
				else
					document.InvokeBreakpointRemoved(this);

				document.InvokeChange();
			}
		}

		/// <summary>
		/// Gets or sets the text of the Row.
		/// </summary>
		[Browsable(true)]
		[Category("Appearance")]
		[Description("Gets or sets the text of the Row.")]
		public string Text
		{
			get { return text; }
			set
			{
				bool parsePreview = false;
				if (text != value)
				{
					parsePreview = true;
					document.Modified = true;
				}

				text = value;
				if (document != null)
				{
					if (parsePreview == true)
					{
						document.Parser.ParsePreviewLine(document.IndexOf(this));
						document.OnApplyFormatRanges(this);
					}

					this.AddToParseQueue();
				}
			}
		}

		/// <summary>
		/// Gets the index of the first word in the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the index of the first word in the Row.")]
		public int StartWordIndex
		{
			get
			{
				if (expansionStartSegment == null)
					return 0;

				Word word = expansionStartSegment.StartWord;

				int wordIndex = 0;
				foreach (Word currentWord in this)
				{
					if (currentWord == word)
						break;

					wordIndex += currentWord.Text.Length;
				}

				return wordIndex;
			}
		}

		/// <summary>
		/// Gets the first non-whitespace word in the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the first non-whitespace word in the Row.")]
		public Word FirstNonWhitespaceWord
		{
			get
			{
				foreach (Word currentWord in this)
				{
					if (currentWord.Type == WordType.Word)
						return currentWord;
				}

				return null;
			}
		}

		/// <summary>
		/// Gets the word at the given index.
		/// </summary>
		/// <param name="index">Index of the word to find.</param>
		/// <returns>The found word.</returns>
		public Word this[int index]
		{
			get
			{
				if (index >= 0)
					return words[index] as Word;
				else
					return new Word();
			}
		}

		/// <summary>
		/// Gets the index of the Row in its parent document.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the index of the Row in its parent document.")]
		public int Index
		{
			get { return document.IndexOf(this); }
		}

		/// <summary>
		/// Gets the index of the Row if it is visible, else gets the first visible Row in the parent document.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the index of the Row if it is visible, else gets the first visible Row in the parent document.")]
		public int VisibleIndex
		{
			get
			{
				int rowIndex = document.VisibleRows.IndexOf(this);
				if (rowIndex == -1)
				{
					if (startSegment != null)
					{
						if (startSegment.StartRow != null)
						{
                            return startSegment.StartRow != this ? startSegment.StartRow.VisibleIndex : this.Index;
						}
						else
							return this.Index;
					}
					else
						return this.Index;
				}
				else
					return rowIndex;
			}
		}

		/// <summary>
		/// Gets the next visible Row from this Row's index.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the next visible Row from this Row's index.")]
		public Row NextVisibleRow
		{
			get
			{
				int rowIndex = this.VisibleIndex;
				if (rowIndex > document.VisibleRows.Count)
					return null;

				if (rowIndex + 1 < document.VisibleRows.Count)
					return document.VisibleRows[rowIndex + 1];
				else
					return null;
			}
		}

		/// <summary>
		/// Gets the next Row from this Row's index.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the next Row from this Row's index.")]
		public Row NextRow
		{
			get
			{
				int rowIndex = this.Index;
				if (rowIndex + 1 <= document.Lines.Length - 1)
					return document[rowIndex + 1];
				else
					return null;
			}
		}

		/// <summary>
		/// Gets the previous visible Row from this Row's index.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the previous visible Row from this Row's index.")]
		public Row PreviousVisibleRow
		{
			get
			{
				int rowIndex = this.VisibleIndex;
				if (rowIndex < 0)
					return null;

				if (rowIndex - 1 >= 0)
					return document.VisibleRows[rowIndex - 1];
				else
					return null;
			}
		}

		/// <summary>
		/// Gets the previous Row from this Row's index.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the previous Row from this Row's index.")]
		public Row PreviousRow
		{
			get
			{
				int rowIndex = this.Index;

				if (rowIndex - 1 >= 0)
					return document[rowIndex - 1];
				else
					return null;
			}
		}

		/// <summary>
		/// Gets whether the Row is collapsed.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets whether the Row is collapsed.")]
		public bool IsCollapsed
		{
			get
			{
				if (expansionStartSegment != null)
				{
					if (expansionStartSegment.Expanded == false)
						return true;
				}

				return false;
			}
		}

		/// <summary>
		/// Gets whether the ending part of the Row is collapsed.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets whether the ending part of the Row is collapsed.")]
		public bool IsCollapsedEndPart
		{
			get
			{
				if (expansionEndSegment != null)
				{
					if (expansionEndSegment.Expanded == false)
						return true;
				}

				return false;
			}
		}

		/// <summary>
		/// Gets whether the Row can be folded.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets whether the Row can be folded.")]
		public bool CanFold
		{
			get { return (expansionStartSegment != null && expansionStartSegment.EndRow != null && document.IndexOf(expansionStartSegment.EndRow) != 0); }
		}

		/// <summary>
		/// Gets whether the ending part of the Row can fold.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets whether the ending part of the Row can fold.")]
		public bool CanFoldEndPart
		{
			get { return expansionEndSegment != null; }
		}

		/// <summary>
		/// Gets or sets whether the Row is expanded.
		/// </summary>
		[Browsable(false)]
		[Category("Appearance")]
		[Description("Gets or sets whether the Row is expanded.")]
		public bool Expanded
		{
			get
			{
                if (this.CanFold == true)
                    return expansionStartSegment.Expanded;
                else
                    return false;
			}
			set
			{
				if (this.CanFold == true)
					expansionStartSegment.Expanded = value;
			}
		}

		/// <summary>
		/// Gets or sets the text of the Row when it is expanded.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[DefaultValue("")]
		[Description("Gets or sets the text of the Row when it is expanded.")]
		public string ExpansionText
		{
			get { return expansionStartSegment.Scope.ExpansionText; }
			set
			{
				Scope oldScope = expansionStartSegment.Scope;
				Scope newScope = new Scope();

				newScope.CaseSensitive   = oldScope.CaseSensitive;
				newScope.CauseIndent     = oldScope.CauseIndent;
				newScope.DefaultExpanded = oldScope.DefaultExpanded;
				newScope.EndPatterns     = oldScope.EndPatterns;
				newScope.NormalizeCase   = oldScope.NormalizeCase;

				newScope.Parent = oldScope.Parent;
				newScope.SpawnBlockOnEnd   = oldScope.SpawnBlockOnEnd;
				newScope.SpawnBlockOnStart = oldScope.SpawnBlockOnStart;

				newScope.Start = oldScope.Start;
				newScope.Style = oldScope.Style;
				newScope.ExpansionText = value;

				expansionStartSegment.Scope = newScope;
				document.InvokeChange();
			}
		}

		/// <summary>
		/// Gets whether the Row has an expansion line.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets whether the Row has an expansion line.")]
		public bool HasExpansionLine
		{ 
			get { return endSegment.Parent != null; }
		}

		/// <summary>
		/// Gets the ending Row of the Row's expansion.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the ending Row of the Row's expansion.")]
		public Row ExpansionEndRow
		{
			get
			{
				if (this.CanFold == true)
					return expansionStartSegment.EndRow;
				else
					return this;
			}
		}

		/// <summary>
		/// Gets the starting Row of the Row's expansion.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the starting Row of the Row's expansion.")]
		public Row ExpansionStartRow
		{
			get
			{
				if (this.CanFoldEndPart == true)
					return expansionEndSegment.StartRow;
				else
					return this;
			}
		}

		/// <summary>
		/// Gets the Row as if it were collapsed.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the Row as if it were collapsed.")]
		public Row VirtualCollapsedRow
		{
			get
			{
				Row row = new Row();

				foreach (Word currentWord in this)
				{
					if (expansionStartSegment == currentWord.Segment)
						break;

					row.Add(currentWord);
				}

				Word word = row.Add(this.CollapsedText);
				word.Style = new TextStyle();

				word.Style.BackColor = Color.Silver;
				word.Style.ForeColor = Color.DarkBlue;
				word.Style.Bold = true;

				bool found = false;
				if (this.ExpansionEndRow != null)
				{
					foreach (Word currentWord in this.ExpansionEndRow)
					{
						if (found == true)
							row.Add(currentWord);

						if (currentWord == ExpansionEndRow.expansionEndSegment.EndWord)
							found = true;
					}
				}

				return row;
			}
		}

		/// <summary>
		/// Gets the text of the Row as if it were collapsed.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the text of the Row as if it were collapsed.")]
		public string CollapsedText
		{
			get
			{
				string subText = "";
				int position = 0;

				foreach (Word currentWord in this)
				{
					position += currentWord.Text.Length;
					if (currentWord.Segment == expansionStartSegment)
					{
						subText = Text.Substring(position).Trim();
						break;
					}
				}

				if (expansionStartSegment.Scope.ExpansionText != "")
					subText = expansionStartSegment.Scope.ExpansionText.Replace("***", subText);

				return subText;
			}
		}

		/// <summary>
		/// Gets the number of words in the Row.
		/// </summary>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets the number of words in the Row.")]
		public int Count
		{
			get { return words.Count; }
		}

		/// <summary>
		/// Gets or sets a value indicating whether this <see cref="Row"/> should be highlighted.
		/// </summary>
		/// <value><c>true</c> if this <see cref="Row"/> should be highlighted; otherwise, <c>false</c>.</value>
		[Browsable(false)]
		[Category("Data")]
		[Description("Gets or sets a value indicating whether this Row should be highlighted.")]
		internal bool Highlight
		{
			get { return highlight; }
			set { highlight = value; }
		}

		#endregion

		#region Methods

		#region Public

		/// <summary>
		/// Clears the Row.
		/// </summary>
		public void Clear()
		{
			words = new WordCollection();
		}

		/// <summary>
		/// Ensures that this Row is visible in its parent document.
		/// </summary>
		public void EnsureVisible()
		{
			if (this.RowState == RowState.NotParsed)
				return;

			Segment segment = startSegment;
			while (segment != null)
			{
				segment.Expanded = true;
				segment = segment.Parent;
			}

			document.ResetVisibleRows();
		}

		/// <summary>
		/// Adds the given text as a Word to the Row.
		/// </summary>
		/// <param name="text">Text to add.</param>
		/// <returns>The added Word.</returns>
		public Word Add(string text)
		{
			Word word = new Word();

			word.Row = this;
			word.Text = text;

			words.Add(word);
			return word;
		}

		/// <summary>
		/// Inserts the specified text.
		/// </summary>
		/// <param name="text">The text.</param>
		/// <param name="index">The index.</param>
		/// <returns>The inserted Word.</returns>
		public Word Insert(string text, int index)
		{
			Word word = new Word();

			word.Row = this;
			word.Text = text;

			words.Insert(index, word);
			return word;
		}

		/// <summary>
		/// Adds the Row to the parent document's parsing queue.
		/// </summary>
		public void AddToParseQueue()
		{
			if (inQueue == false)
				document.ParseQueue.Add(this);

			inQueue = true;
			this.RowState = RowState.NotParsed;
		}

		/// <summary>
		/// Sets the text of the Row.
		/// </summary>
		/// <param name="text">The text that should be the Row's text.</param>
		public void SetText(string text)
		{
			document.StartUndoCapture();

			TextPoint textPoint = new TextPoint(0, Index);
			TextRange textRange = new TextRange();

			textRange.FirstColumn = 0;
			textRange.FirstRow = textPoint.Y;
			textRange.LastColumn = Text.Length;
			textRange.LastRow = textPoint.Y;

			document.StartUndoCapture();

			document.PushUndoBlock(UndoAction.DeleteRange, document.GetRange(textRange), textRange.FirstColumn, textRange.FirstRow);
			document.PushUndoBlock(UndoAction.InsertRange, Text, textPoint.X, textPoint.Y);

			this.Text = text;
			document.EndUndoCapture();
			document.InvokeChange();
		}

		/// <summary>
		/// Updates the casing of all words in the Row.
		/// </summary>
		public void MatchCase()
		{
			string wordText = "";
			foreach (Word word in words)
				wordText = wordText + word.Text;

			text = wordText;
		}

		/// <summary>
		/// Returns the IEnumerator of the Row.
		/// </summary>
		/// <returns></returns>
		public IEnumerator GetEnumerator()
		{
			return words.GetEnumerator();
		}

		/// <summary>
		/// Parses the Row.
		/// </summary>
		public void Parse()
		{
			document.ParseRow(this);
		}

		/// <summary>
		/// Parses the Row.
		/// </summary>
		/// <param name="parseKeywords">Whether to parse keywords.</param>
		public void Parse(bool parseKeywords)
		{
			document.ParseRow(this, parseKeywords);
		}

		/// <summary>
		/// Sets the expansion Segment of the Row.
		/// </summary>
		public void SetExpansionSegment()
		{
			expansionStartSegment = null;
			expansionEndSegment = null;
			foreach (Segment segment in startSegments)
			{
				if (endSegments.Contains(segment) == false)
				{
					expansionStartSegment = segment;
					break;
				}
			}

			foreach (Segment segment in endSegments)
			{
				if (startSegments.Contains(segment) == false)
				{
					expansionEndSegment = segment;
					break;
				}
			}

			if (expansionEndSegment != null)
				expansionStartSegment = null;
		}

		/// <summary>
		/// Returns the leading whitespace of the Row's text.
		/// </summary>
		/// <returns>The leading whitespace of the Row's text.</returns>
		public string GetLeadingWhitespace()
		{
			string rowText = text;
			int index = 0;
			rowText = rowText.Replace("	", " ");
			for (index = 0; index < rowText.Length; index++)
			{
				if (rowText.Substring(index, 1) != " ")
					break;
			}

			return text.Substring(0, index);
		}

		/// <summary>
		/// Returns the leading virtual whitespace.
		/// </summary>
		/// <returns>The leading virtual whitespace.</returns>
		public string GetVirtualLeadingWhitespace()
		{
			int index = this.StartWordIndex;
			string whitespace = "";
			foreach (char c in this.Text)
			{
				if (c == '\t')
					whitespace += c;
				else
					whitespace += ' ';

				index--;
				if (index <= 0)
					break;
			}

			return whitespace;
		}

		/// <summary>
		/// Adds the given word to the Row.
		/// </summary>
		/// <param name="word">Row to add.</param>
		public void Add(Word word)
		{
			words.Add(word);
		}

		/// <summary>
		/// Returns the index of the given word.
		/// </summary>
		/// <param name="word">Word to found index of.</param>
		/// <returns>The index of the given word.</returns>
		public int IndexOf(Word word)
		{
			return words.IndexOf(word);
		}

		/// <summary>
		/// Finds the next word to the right from the start word that has the given PatternList as parent.
		/// </summary>
		/// <param name="patternList">Parent PatternList.</param>
		/// <param name="startWord">Starting word.</param>
		/// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
		/// <returns>The found word.</returns>
		public Word FindRightWordByPatternList(PatternList patternList, Word startWord, bool ignoreStartWord)
		{
			int index = startWord.Index;
			if (ignoreStartWord == true)
				index++;

			Word word = null;
			while (index < words.Count)
			{
				word = this[index];
				if (word.Pattern != null)
				{
					if (word.Pattern.Parent != null)
					{
						if (word.Pattern.Parent == patternList && word.Type != WordType.Space && word.Type != WordType.Tab)
							return word;
					}
				}

				index++;
			}

			return null;
		}

		/// <summary>
		/// Finds the next word to the right from the start word that has the given PatternList found from the given name as parent.
		/// </summary>
		/// <param name="patternListName">Name of the parent PatternList.</param>
		/// <param name="startWord">Starting word.</param>
		/// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
		/// <returns>The found word.</returns>
		public Word FindRightWordByPatternListName(string patternListName, Word startWord, bool ignoreStartWord)
		{
			int index = startWord.Index;
			if (ignoreStartWord == true)
				index++;

			Word word = null;
			while (index < words.Count)
			{
				word = this[index];
				if (word.Pattern != null)
				{
					if (word.Pattern.Parent != null)
					{
						if (word.Pattern.Parent.Name == patternListName && word.Type != WordType.Space && word.Type != WordType.Tab)
							return word;
					}
				}

				index++;
			}

			return null;
		}

		/// <summary>
		/// Finds the next word to the left from the start word that has the given PatternList as parent.
		/// </summary>
		/// <param name="patternList">Parent PatternList.</param>
		/// <param name="startWord">Starting word.</param>
		/// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
		/// <returns>The found word.</returns>
		public Word FindLeftWordByPatternList(PatternList patternList, Word startWord, bool ignoreStartWord)
		{
			int index = startWord.Index;
			if (ignoreStartWord == true)
				index--;

			Word word = null;
			while (index >= 0)
			{
				word = this[index];
				if (word.Pattern != null)
				{
					if (word.Pattern.Parent != null)
					{
						if (word.Pattern.Parent == patternList && word.Type != WordType.Space && word.Type != WordType.Tab)
							return word;
					}
				}

				index--;
			}

			return null;
		}

		/// <summary>
		/// Finds the next word to the left from the start word that has the given PatternList found from the given name as parent.
		/// </summary>
		/// <param name="patternListName">Name of the parent PatternList.</param>
		/// <param name="startWord">Starting word.</param>
		/// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
		/// <returns>The found word.</returns>
		public Word FindLeftWordByPatternListName(string patternListName, Word startWord, bool ignoreStartWord)
		{
			int index = startWord.Index;
			if (ignoreStartWord == true)
				index--;

			Word word = null;
			while (index >= 0)
			{
				word = this[index];
				if (word.Pattern != null)
				{
					if (word.Pattern.Parent != null)
					{
						if (word.Pattern.Parent.Name == patternListName && word.Type != WordType.Space && word.Type != WordType.Tab)
							return word;
					}
				}

				index--;
			}

			return null;
		}

		/// <summary>
		/// Finds the next word to the right from the start word that has the given Block as parent.
		/// </summary>
		/// <param name="block">Parent Block.</param>
		/// <param name="startWord">Starting word.</param>
		/// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
		/// <returns>The found word.</returns>
		public Word FindRightWordByBlock(Block block, Word startWord, bool ignoreStartWord)
		{
			int index = startWord.Index;
			if (ignoreStartWord == true)
				index++;

			Word word = null;
			while (index < words.Count)
			{
				word = this[index];
				if (word.Segment.Block == block && word.Type != WordType.Space && word.Type != WordType.Tab)
					return word;

				index++;
			}

			return null;
		}

		/// <summary>
		/// Finds the next word to the right from the start word that has the given Block found from the given name as parent.
		/// </summary>
		/// <param name="blockName">Name of the parent Block.</param>
		/// <param name="startWord">Starting word.</param>
		/// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
		/// <returns>The found word.</returns>
		public Word FindRightWordByBlockName(string blockName, Word startWord, bool ignoreStartWord)
		{
			int index = startWord.Index;
			if (ignoreStartWord == true)
				index++;

			Word word = null;
			while (index < words.Count)
			{
				word = this[index];
				if (word.Segment.Block.Name == blockName && word.Type != WordType.Space && word.Type != WordType.Tab)
					return word;

				index++;
			}

			return null;
		}

		/// <summary>
		/// Finds the next word to the left from the start word that has the given Block as parent.
		/// </summary>
		/// <param name="block">Parent Block.</param>
		/// <param name="startWord">Starting word.</param>
		/// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
		/// <returns>The found word.</returns>
		public Word FindLeftWordByBlock(Block block, Word startWord, bool ignoreStartWord)
		{
			int index = startWord.Index;
			if (ignoreStartWord == true)
				index--;

			Word word = null;
			while (index >= 0)
			{
				word = this[index];
				if (word.Segment.Block == block && word.Type != WordType.Space && word.Type != WordType.Tab)
					return word;

				index--;
			}

			return null;
		}

		/// <summary>
		/// Finds the next word to the left from the start word that has the given Block found from the given name as parent.
		/// </summary>
		/// <param name="blockName">Name of the parent Block.</param>
		/// <param name="startWord">Starting word.</param>
		/// <param name="ignoreStartWord">Whether to ignore the starting word.</param>
		/// <returns>The found word.</returns>
		public Word FindLeftWordByBlockName(string blockName, Word startWord, bool ignoreStartWord)
		{
			int index = startWord.Index;
			if (ignoreStartWord == true)
				index--;

			Word word = null;
			while (index >= 0)
			{
				word = this[index];
				if (word.Segment.Block.Name == blockName && word.Type != WordType.Space && word.Type != WordType.Tab)
					return word;

				index--;
			}

			return null;
		}

		#endregion

		#endregion

		/// <summary>
		/// Initializes a new instance of Row.
		/// </summary>
		public Row()
		{
		}
	}
}
